<?php

/*
 * Copyright (c) 2012-2013 Aalto University and University of Helsinki
 * MIT License
 * see LICENSE.txt for more information
 */

class Concept extends VocabularyDataObject {

  private $properties;
  private $foundby;

  public function __construct($model, $vocab, $resource) {
    parent::__construct($model, $vocab, $resource);

    $this->properties = array();

    $members_array = array();
    $long_uris = $resource->propertyUris();

    foreach ($long_uris as &$prop) {
      if (EasyRdf_Namespace::shorten($prop)) // shortening property labels if possible
        $prop = EasyRdf_Namespace::shorten($prop);

      // Iterating through every literal and adding these to the data object.
      foreach ($resource->allLiterals($prop, $this->lang) as $val) {
        $this->addProperty($prop, null, null, $val->getLang(), $val->getValue());
      }

      // Iterating through every resource and adding these to the data object.
      foreach ($resource->allResources($prop) as $val2) {
        $label = null;
        $label_lang = null;
        $exvocab = null;

        if ($prop === 'skos:narrower' && $val2->hasProperty('skos:member')) { // if collection group has been found  
          $members_array = array_merge($this->getGroupMembers($val2, $prop, $resource), $members_array);
          // $this->mergeNarrowerProperties($members_array);
          continue;
        }
        if ($prop === 'skos:exactMatch' || $prop === 'skos:narrowMatch' || $prop === 'skos:broadMatch' || $prop === 'owl:sameAs' || $prop === 'skos:closeMatch' ) {
          $propResult = $this->getExternalLabel($val2);
          if ($propResult) {
            $default_lang = $this->model->guessVocabularyFromURI($val2->getUri())->getDefaultLanguage();
            foreach ($propResult as $row) { // for choosing the UI-language label or if not found, the first language for external label
              if ($row->exlabel->getLang() == $default_lang || !(isset($label))) {
                $label = $row->exlabel->getValue();
                $label_lang = isset($default_lang) ? $default_lang : $row->exlabel->getLang();
                $exvocab = $this->model->guessVocabularyFromURI($val2->getUri())->getId();
              } 
            }
          }
          if (!isset($label_lang) || (!isset($label))) {
            $label = $val2->getUri();
            $label_lang = $this->lang;
          }
        }
        $properties = $this->getPropertyParam($val2, $prop);
        if ($properties['label'] == null) {
          $properties['label'] = $label;
          $properties['lang'] = $label_lang;
          $properties['exvocab'] = $exvocab;
        }
        $this->setProperties($properties);
      }
    }
    // sorting the properties to a order preferred in the ONKI concept page.
    $this->setSortedProperties($this->arbitrarySort($this->getProperties()));

    if (!empty($members_array)) // if skos:narrower properties are actually groups we need to remove duplicates.
      $this->membersToDAOArray($members_array);

  }

  /**
   * Generates and makes a query into a external vocabulary for an exact
   * match for a particular concept.
   * @param type $val
   * @return string
   */
  private function getExternalLabel($val) {
    $exuri = $val->getUri();

    $voc = $this->model->guessVocabularyFromURI($exuri);
    if ($voc) {
      $exsparql = $voc->getSparql();
      return $exsparql->queryExternalLabel($exuri);
    } else {
      return null;
    }
  }
                                                           

  private function getPropertyParam($val, $prop = null) {
    $ret = array();
    $ret['label'] = $val->label($this->lang) ? $val->label($this->lang)->getValue() : null;
    $ret['concept_uri'] = $val->getUri();
    $ret['vocab'] = $this->getVocab();
    $ret['prop'] = ($prop ? $ret['prop'] = $prop : $val->getUri());
    $ret['lang'] = $this->lang;
    $ret['exvocab'] = $ret['vocab'];
    return $ret;
  }

  private function membersToDAOArray($membersArray) {
    foreach ($membersArray as $topConcept) {
      $topDAO = $this->addProperty('skos:narrower', $topConcept['parts'], $topConcept['vocab'], null, $topConcept['label'], $exvocab = null);
      foreach ($topConcept['sub_members'] as $subMember) {
        $topDAO->addSubMember('onki:sub', $subMember['label'], $subMember['parts'], $subMember['vocab'], null, $subMember['external']);
        $this->deleteProperty('skos:narrower', $subMember['label']);
      }
    }
    $this->sortProperties('skos:narrower');
  }

  private function getGroupMembers($rdfProperty, $prop, $resource) {
    $members_array = Array();
    $prop_info = $this->getPropertyParam($rdfProperty);
    $external = false;
    if (strstr($prop_info['concept_uri'], 'http')) // for identifying concepts that are found with a uri not consistent with the current vocabulary
      $external = true;
    $members_array[$rdfProperty->getUri()] = array('type' => 'resource', 'label' => $prop_info['label'],
        'uri' => $prop_info['concept_uri'], 'vocab' => $prop_info['vocab'], 'parts' => $rdfProperty->getUri(), 'external' => $external);
    foreach ($rdfProperty->allResources('skos:member') as $member) {
      $arska = $resource->allResources($prop);
      foreach ($arska as $tulos) {
        if ($tulos->getUri() === $member->getUri()) {
          $prop_info = $this->getPropertyParam($tulos);
          $external = false;
          if (strstr($prop_info['concept_uri'], 'http')) // for identifying concepts that are found with a uri not consistent with the current vocabulary
            $external = true;
          if ($prop_info['label'] == null) { // fixes json encoded unicode characters causing labels to disappear in afo
            $prop_info['label'] = ('"' . $prop_info['concept_uri'] . '"');
            $prop_info['label'] = json_decode($prop_info['label']);
            $prop_info['concept_uri'] = $prop_info['label'];
            $prop_info['label'] = strtr($prop_info['label'], '_', ' ');
          }
          $members_array[$rdfProperty->getUri()]['sub_members'][] = array('type' => 'resource', 'label' => $prop_info['label'],
              'uri' => $prop_info['concept_uri'], 'vocab' => $prop_info['vocab'], 'parts' => $tulos->getUri(), 'external' => $external);
        }
      }
    }
    return $members_array;
  }

  /**$ret[$uri]['parent'] =
   * Sorting the result list to a arbitrary order defined below in mycompare()
   * @param unknown_type $sortable
   */
  private function arbitrarySort($sortable) {
    // sorting the result list to a arbitrary order defined below in mycompare()
    if ($sortable == null)
      return $sortable;
    uksort($sortable, array($this, 'mycompare'));
    foreach ($sortable as &$property_list)
      $property_list = $this->array_sorter($property_list, 'label');

    return $sortable;
  }

  /**
   * Compares the given objects and returns -1 or 1 depending which ought to be first.
   * $order defines the priorities of the different properties possible in the array.
   * @param string $a the first item to be compared
   * @param string $b the second item to be compared
   */
  private static function mycompare($a, $b) {
    if ($a == $b) {
      return 0;
    }
    $order = array("skos:note", "skos:broader", "skos:narrower", "skos:related", "skos:altLabel");
    $position = array_search($a, $order);
    $position2 = array_search($b, $order);

    //if both are in the $order, then sort according to their order in $order...
    if ($position2 !== false && $position !== false) {
      return ($position < $position2) ? -1 : 1;
    }
    //if only one is in $order, then sort to put the one in $order first...
    if ($position !== false) {
      return -1;
    }
    if ($position2 !== false) {
      return 1;
    }

    //if neither in $order, then a simple alphabetic sort...
    return ($a < $b) ? -1 : 1;
  }

  /**
   * Sorting function for sorting arrays by a named subarray value..
   * for example ['label'] in arbitrary sort.
   * @param array $array
   * @param type $on
   * @param enum $order PHP Sort options attribute eg. SORT_DESC
   * @return array which should now be sorted.
   */
  private function array_sorter($array, $on, $order = SORT_ASC) {
    $new_array = array();
    $sortable_array = array();

    if (count($array) > 0) {
      foreach ($array as $k => $v) {
        if (is_array($v)) {
          foreach ($v as $k2 => $v2) {
            if ($k2 == $on) {
              $sortable_array[$k] = strtolower($v2);
            }
          }
        } else {
          $sortable_array[$k] = $v;
        }
      }

      switch ($order) {
        case SORT_ASC:
          asort($sortable_array);
          break;
        case SORT_DESC:
          arsort($sortable_array);
          break;
      }

      foreach ($sortable_array as $k => $v) {
        $new_array[$k] = $array[$k];
      }
    }

    return $new_array;
  }

  private function addProperty($proplabel, $uri, $vocab, $lang = null, $label = null, $exvocab = null) {
    $newProp = new ConceptProperty($proplabel, $label, $uri, $vocab, $lang, $exvocab);
    $this->properties[$proplabel][] = $newProp;
    return $newProp;
  }
  
  private function sortProperties($property) {
    uksort($this->properties[$property], 'strcasecmp');
  }

  private function setProperties($array) {
    $this->addProperty($array['prop'], $array['concept_uri'], $array['vocab'], $array['lang'], $array['label'], $array['exvocab']);
  }

  private function mergeNarrowerProperties($array) {
    if (isset($this->properties['skos:narrower']))
      $this->properties['skos:narrower'] = array_merge($array, $this->properties['skos:narrower']);
  }

  private function deleteProperty($property, $value = null) {
    if ($value == null) {
      if (isset($this->properties[$property])) {
        unset($this->properties[$property]);
      }
      return;
    }
    foreach ($this->properties[$property] as $key => $prop)
      if ($prop->getValue() === $value)
        unset($this->properties[$property][$key]);
  }

  private function setSortedProperties($array) {
    $this->properties = $array;
    $this->cleanUp();
  }


  /**
   * Hides unwanted properties.
   */
  private function cleanUp() {
    /* checking if unwanted skos transitives are found and if so removing them from the view */
    $this->deleteProperty('skosext:broaderTransitive');
    $this->deleteProperty('skosext:narrowerTransitive');
    /* checking if unwanted generics are found and if so removing them from the view */
    $this->deleteProperty('skosext:narrowerGeneric');
    $this->deleteProperty('skosext:broaderGeneric');

    $this->deleteProperty('skos:prefLabel');
    $this->deleteProperty('skos:hiddenLabel');
    $this->deleteProperty('rdf:type');
    $this->deleteProperty('skos:inScheme');
    $this->deleteProperty('owl:sameAs');
    $this->deleteProperty('skos:topConceptOf');
  }

  public function getUri() {
    return $this->resource->getUri();
  }

  public function getForeignLabels() {
    $labels = array();
    foreach($this->resource->allLiterals('skos:prefLabel') as $lit) {
      if ($lit->getLang() != $this->lang)
        $labels[$lit->getLang()] = $lit->getValue();
    }
    return $labels;
  }

  public function getLabel() {
    return $this->resource->label($this->lang) ? $this->resource->label($this->lang)->getValue() : null;
  }

  public function getVocab() {
    return $this->vocab ? $this->vocab->getId() : null;
  }

  public function setFoundBy($altlabel) {
    $this->foundby = $altlabel;
  }

  public function getFoundBy() {
    return $this->foundby;
  }
  
  /**
   * Get the type of the concept, as a human-readable label if possible.
   * @return string type of concept as label, or URI if no label is available
   */

  public function getType() {
    // determine all possible types for this concept, with uri and label
    $types = array();
    foreach ($this->resource->allResources('rdf:type') as $typeres) {
      // use shortened URI if possible; if not, use full URI
      $uri = $typeres->shorten() ? $typeres->shorten() : $typeres->getUri();
      $types[$uri] = $typeres->label($this->lang);
    }
    // if there are multiple types, disregard skos:Concept and prefer
    // instead the more specific type(s)
    if (sizeof($types) > 1 && isset($types['skos:Concept'])) {
      unset($types['skos:Concept']);
    }
    // return one of the types
    foreach ($types as $uri => $label) {
      if ($label) return $label; // prefer returning label
      return $uri; // if no label is found, return the (shortened/full) URI
    }
  }


  public function getProperties() {
    return $this->properties;
  }

}

class ConceptProperty {

  private $lang;
  private $exvocab;
  private $type;
  private $value;
  private $uri;
  private $vocab;
  private $submembers;

  public function __construct($type, $value, $uri, $vocab, $lang, $exvocab = null) {
    $this->submembers = array();
    $this->lang = $lang;
    $this->exvocab = $exvocab;
    $this->type = $type;
    $this->value = $value;
    $this->uri = $uri;
    $this->vocab = $vocab;
  }

  public function setLang($param) {
    $this->lang = $param;
  }

  public function getLang() {
    return $this->lang;
  }

  public function setExVocab($param) {
    $this->exvocab = $param;
  }

  public function getExVocab() {
    return $this->exvocab;
  }

  public function setType($param) {
    $this->type = $param;
  }

  public function getType() {
    return $this->type;
  }

  public function setValue($param) {
    $this->value = $param;
  }

  public function getValue() {
    return $this->value;
  }

  public function getUri() {
    return $this->uri;
  }

  public function getVocab() {
    return $this->vocab;
  }

  public function addSubMember($type, $value, $uri, $vocab, $lang, $exvocab = null) {
    $this->submembers[$value] = new ConceptProperty($type, $value, $uri, $vocab, $lang, $exvocab = null);
    $this->sortSubMembers();
  }

  public function getSubMembers() {
    if (empty($this->submembers))
      return null;
    return $this->submembers;
  }

  public function sortSubMembers() {
    if (!empty($this->submembers))
      ksort($this->submembers);
  }

}

?>
